/**
 * Copyright 2013 yangming.liu<liuyangming@gmail.com>.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, see <http://www.gnu.org/licenses/>.
 */
package org.bytesoft.openjta;

import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;

import org.bytesoft.openjta.internal.AssociateContext;
import org.bytesoft.openjta.internal.KeyInstance;
import org.bytesoft.openjta.supports.XAResourceMarshaller;
import org.bytesoft.openjta.supports.XidFactory;
import org.bytesoft.openjta.supports.schedule.CleanupProcessor;
import org.bytesoft.openjta.supports.schedule.TimingProcessor;
import org.bytesoft.openjta.xa.XidImpl;

public class TransactionManagerImpl implements TransactionManager, TimingProcessor, CleanupProcessor {
	private final Map<Thread, AssociateContext> threadToTxMap = new ConcurrentHashMap<Thread, AssociateContext>();

	private KeyInstance keyInstance;
	private XidFactory xidFactory;
	private TransactionRepository transactionRepository;
	private XAResourceMarshaller<Serializable> xaResourceMarshaller;
	private boolean lroEnabled = false;
	private int transactionTimeout = TransactionConstants.DEFAULT_TX_TIMEOUT_SECONDS;
	private CleanupProcessor delegateCleanupProcessor;

	@Override
	public void begin() throws NotSupportedException, SystemException {
		TransactionImpl transaction = this.getTransaction();
		if (transaction != null) {
			throw new NotSupportedException();
		}
		transaction = new TransactionImpl();
		transaction.setXaResourceMarshaller(this.xaResourceMarshaller);
		transaction.setTransactionManager(this);
		transaction.setTransactionLogger(this.transactionRepository.getTransactionLogger());

		transaction.setLroEnabled(this.lroEnabled);
		transaction.setTransactionTimeout(this.transactionTimeout);
		transaction.initialize();

		XidImpl globalXid = transaction.getGlobalXid();
		System.out.printf("[%s] begin: coordinator= %s, local-res-allow= %s%n", globalXid, true,
				transaction.isLocalResourceAllowed());
		this.transactionRepository.putTransaction(globalXid, transaction);
		this.associateTransaction(transaction);
	}

	public void begin(TransactionContext transactionContext) throws SystemException {
		XidImpl branchXid = transactionContext.getXid();
		XidImpl globalXid = branchXid.getGlobalXid();
		TransactionImpl transaction = this.transactionRepository.getTransaction(globalXid);
		if (transaction == null) {
			transaction = new TransactionImpl(transactionContext);
			transaction.setXaResourceMarshaller(this.xaResourceMarshaller);
			transaction.setTransactionManager(this);
			transaction.setTransactionLogger(this.transactionRepository.getTransactionLogger());

			transaction.setLroEnabled(this.lroEnabled);
			long timeout = transactionContext.getExpiredTime() - transactionContext.getCreatedTime();
			transaction.setTransactionTimeout((int) (timeout / TransactionConstants.SECOND_MILLIS));
			transaction.initialize();
		}
		System.out.printf("[%s] begin: coordinator= %s, local-res-allow= %s%n", globalXid,
				transactionContext.isCoordinator(), transaction.isLocalResourceAllowed());
		this.transactionRepository.putTransaction(globalXid, transaction);
		this.associateTransaction(transaction);
	}

	@Override
	public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException,
			RollbackException, SecurityException, SystemException {
		Transaction transaction = this.getTransaction();
		try {
			transaction.commit();
		} finally {
			this.unassociateTransaction();
		}
	}

	@Override
	public int getStatus() throws SystemException {
		Transaction transaction = this.getTransaction();
		if (transaction == null) {
			return Status.STATUS_NO_TRANSACTION;
		} else {
			return transaction.getStatus();
		}
	}

	@Override
	public TransactionImpl getTransaction() throws SystemException {
		Thread current = Thread.currentThread();
		AssociateContext context = this.threadToTxMap.get(current);
		if (context == null) {
			return null;
		} else {
			return context.getTransaction();
		}
	}

	public TransactionImpl getCurrentTransaction() {
		Thread current = Thread.currentThread();
		AssociateContext context = this.threadToTxMap.get(current);
		if (context == null) {
			return null;
		} else {
			return context.getTransaction();
		}
	}

	public TransactionImpl unassociateTransaction() throws SystemException {
		Thread current = Thread.currentThread();
		AssociateContext context = this.threadToTxMap.remove(current);
		if (context == null) {
			return null;
		} else {
			return context.getTransaction();
		}
	}

	public void associateTransaction(TransactionImpl transaction) throws SystemException {
		Thread current = Thread.currentThread();
		AssociateContext context = new AssociateContext();
		context.setTransaction(transaction);
		context.setThread(current);
		this.threadToTxMap.put(current, context);
	}

	public void completeTransaction(TransactionImpl transaction) {
		XidImpl globalXid = transaction.getGlobalXid();
		this.transactionRepository.removeTransaction(globalXid);
		this.transactionRepository.removeErrorTransaction(globalXid);
	}

	@Override
	public void resume(Transaction transaction) throws IllegalStateException, InvalidTransactionException,
			SystemException {
		if (transaction == null) {
			throw new InvalidTransactionException();
		} else if (!TransactionImpl.class.isInstance(transaction)) {
			throw new InvalidTransactionException();
		}
		TransactionImpl tx = (TransactionImpl) transaction;
		tx.resumeAllResource();
		this.associateTransaction(tx);
	}

	@Override
	public void rollback() throws IllegalStateException, SecurityException, SystemException {
		Transaction transaction = this.getTransaction();
		try {
			transaction.rollback();
		} finally {
			this.unassociateTransaction();
		}
	}

	@Override
	public void setRollbackOnly() throws IllegalStateException, SystemException {
		Transaction transaction = this.getTransaction();
		transaction.setRollbackOnly();
	}

	@Override
	public void setTransactionTimeout(int timeout) throws SystemException {
		this.transactionTimeout = timeout;
	}

	@Override
	public Transaction suspend() throws SystemException {
		TransactionImpl transaction = this.unassociateTransaction();
		transaction.suspendAllResource();
		return transaction;
	}

	public XidImpl createGlobalXid() {
		return this.xidFactory.createGlobalXid();
	}

	public XidImpl createBranchXid(TransactionContext transactionContext) {
		XidImpl xid = transactionContext.getXid();
		return this.xidFactory.createBranchXid(xid);
	}

	public XidImpl createBranchXid(XidImpl xid) {
		return this.xidFactory.createBranchXid(xid);
	}

	public KeyInstance getKeyInstance() {
		if (this.keyInstance == null) {
			this.initializeKeyInstance();
		}
		return this.keyInstance;
	}

	public synchronized void initializeKeyInstance() {
		if (this.keyInstance == null) {
			XidImpl xid = this.xidFactory.createGlobalXid();
			byte[] token = xid.getGlobalTransactionId();
			this.keyInstance = new KeyInstance(token);
		}
	}

	@Override
	public void timingTransactions() {
		// TODO
	}

	@Override
	public void expireTransactions() {
		// TODO
	}

	@Override
	public void registerTransaction(TransactionImpl transaction) {
		this.delegateCleanupProcessor.registerTransaction(transaction);
	}

	public TransactionRepository getTransactionRepository() {
		return transactionRepository;
	}

	public void setTransactionRepository(TransactionRepository transactionRepository) {
		this.transactionRepository = transactionRepository;
	}

	public XAResourceMarshaller<Serializable> getXaResourceMarshaller() {
		return xaResourceMarshaller;
	}

	public void setXaResourceMarshaller(XAResourceMarshaller<Serializable> xaResourceMarshaller) {
		this.xaResourceMarshaller = xaResourceMarshaller;
	}

	public XidFactory getXidFactory() {
		return xidFactory;
	}

	public void setXidFactory(XidFactory xidFactory) {
		this.xidFactory = xidFactory;
	}

	public void setLroEnabled(boolean lroEnabled) {
		this.lroEnabled = lroEnabled;
	}

	public void setDelegateCleanupProcessor(CleanupProcessor delegateCleanupProcessor) {
		this.delegateCleanupProcessor = delegateCleanupProcessor;
	}

}
